#region Copyright 2009 by Mike Podonyi, Licensed under the GNU Lesser General Public License
/*  This file is part of Console.Net.

 *  Console.Net is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.

 *  Console.Net is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Console.Net.  If not, see <http://www.gnu.org/licenses/>.
 */
#endregion

using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Collections.ObjectModel;

namespace Empi.Consolenet
{
    internal class CommandDomBuilder
    {
        internal static CommandDom GetCommandDom(DataBucket dataBucket)
        {
            return
                new CommandDom(dataBucket);
        }
    }

    internal class DomElementKeyedCollection<T>:KeyedCollection<string,T>
        where T : ConsoleNetDomBase
    {

        protected override string GetKeyForItem(T item)
        {
            return item.Name;
        }

    }

    internal abstract class ConsoleNetDomBase
    {
        protected ConsoleNetDomBase(DataBucket bucket,XElement xelement)
        {
            Element = xelement;
            Bucket = bucket;
        }

        protected DataBucket Bucket
        { get; private set; }

        internal XElement Element
        { get; private set; }

        internal virtual string Name
        {
            get
            {
                return Element.Attribute("name").Value;
            }
        }

        internal T1 Annotation<T1>() where T1:class
        {
            
            return Element.Annotation<T1>();
        }

        internal void AddAnnotation(object annotation)
        {
            Element.AddAnnotation(annotation);
        }
    }

    internal abstract class ConsoleNetSetDomBase<T>: IEnumerable<T>
        where T : ConsoleNetDomBase
    {
        protected ConsoleNetSetDomBase(DataBucket bucket,XElement xcommand)
        {
            this.Bucket = bucket;
            this.xcommand = xcommand;
        }

        protected DataBucket Bucket
        {
            get; private set;
        }

        protected XElement xcommand
        {
            get; private set;
        }
        private DomElementKeyedCollection<T> DomElementCollection=new DomElementKeyedCollection<T>();

        private bool DomElementCollectionInitialized = false;
        private void InitializeDomCollection()
        {
            if(DomElementCollectionInitialized)
                return;

            foreach(var i in jjj())
            {
                DomElementCollection.Add(i);
            }

            DomElementCollectionInitialized = true;
        }

        protected abstract IEnumerable<T> jjj();

        internal T this[string name] 
        {
            get
            {
                InitializeDomCollection();
                return DomElementCollection[name];
            }
        }

        protected T this[int index]
        {
            get
            {
                InitializeDomCollection();
                return DomElementCollection[index];
            }
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            InitializeDomCollection();

            return DomElementCollection.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }

    internal class OptionSetDom : ConsoleNetSetDomBase<OptionDom>, IOptionSet
    {

        internal OptionSetDom(DataBucket bucket,XElement xcommand)
            : base(bucket, xcommand)
        {
        }

        protected override IEnumerable<OptionDom> jjj()
        {
            return
                (from i in xcommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option")
                select new OptionDom(Bucket,i)).Concat(Bucket.GlobalOptions);
        }


        #region IOptionSet Members

        IOption IOptionSet.this[string name]
        {
            get { return base[name]; }
        }

        IOption IOptionSet.this[int index]
        {
            get { return base[index]; }
        }

        #endregion

        #region IEnumerable<IOption> Members

        IEnumerator<IOption> IEnumerable<IOption>.GetEnumerator()
        {
            return this.GetEnumerator() as IEnumerator<IOption>;
        }

        #endregion

        //#region IEnumerable Members

        //System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        //{
        //    return base.GetEnumerator();
        //}

        //#endregion
    }

    internal class ArgumentSetDom : ConsoleNetSetDomBase<ArgumentDom>, IArgumentSet
    {

        internal ArgumentSetDom(DataBucket bucket, XElement xcommand)
            : base(bucket, xcommand)
        {
        }

        protected override IEnumerable<ArgumentDom> jjj()
        {
            return
                (from i in xcommand.Elements(DataBucket.MainNameSpace + "arguments").Elements(DataBucket.MainNameSpace + "argument")
                 select new ArgumentDom(Bucket, i));
        }


        #region IArgumentSet Members

        string[] IArgumentSet.this[string name]
        {
            get
            {
                return base[name].ToArray();
            }
        }

        string IArgumentSet.this[int index]
        {
            get
            {
                return ((IEnumerable<string>)this).ElementAt(index);
            }
        }

        #endregion

        #region IEnumerable<string> Members

        IEnumerator<string> IEnumerable<string>.GetEnumerator()
        {
            return (from argd in ((IEnumerable<ArgumentDom>)this)
                     from argva in argd
                     select argva).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<string>)this).GetEnumerator();
        }

        #endregion
    }

    internal class CommandSetDom: ConsoleNetSetDomBase<CommandDom>, ICommandSet
    {
        internal CommandSetDom(DataBucket bucket, XElement xcommand, CommandDom parentCommand):base(bucket,xcommand)
        {
            ParentCommand = parentCommand;
        }

        private readonly CommandDom ParentCommand;

        protected override IEnumerable<CommandDom> jjj()
        {
            return
                from i in xcommand.Elements(DataBucket.MainNameSpace + "commands").Elements(DataBucket.MainNameSpace + "command")
                select new CommandDom(Bucket, i, ParentCommand);
        }




        #region ICommandSet Members

        ICommand ICommandSet.this[string name]
        {
            get { return base[name]; }
        }

        ICommand ICommandSet.this[int index]
        {
            get { return base[index]; }
        }

        #endregion

        #region IEnumerable<ICommand> Members

        IEnumerator<ICommand> IEnumerable<ICommand>.GetEnumerator()
        {
            foreach (var dom in this)
            {
                yield return dom;
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<ICommand>) this).GetEnumerator();
        }

        #endregion
    }

    internal class ArgumentDom: ConsoleNetDomBase, IEnumerable<string>
    {
        internal ArgumentDom(DataBucket bucket, XElement xcommand)
            : base(bucket, xcommand)
        {


        }

        internal void AddArgument(string p)
        {
            arguments.Add(p);
        }

        private readonly List<string> arguments = new List<string>();

        internal string this[int index]
        { get { return arguments[index]; } }

        #region IEnumerable<string> Members

        public IEnumerator<string> GetEnumerator()
        {
            return arguments.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }

    internal class OptionDom : ConsoleNetDomBase, IOption
    {
        internal OptionDom(DataBucket bucket,XElement xcommand)
            : base(bucket,xcommand)
        {


        }

        public bool IsGlobal
        {
            get
            {
                return Element.Parent.Name.Equals(DataBucket.MainNameSpace + "globaloptions");
            }
        }

        internal string Token
        {
            get
            {
                return Element.Attribute("value").Value;
            }
        }

        //private string _Value;
        //internal string Value
        //{
        //    get { return _Value; }
        //    set { Choosen = true;
        //        _Value = value; }
        //}

        internal bool Choosen
        {
            get; set;
        }

        

        internal void AddArgument(string p)
        {
            arguments.Add(p);
        }

        private List<string> arguments=new List<string>();

        #region IOption Members

        string IOption.Name
        {
            get { return this.Name; }
        }

        string IOption.Value
        {
            get { return arguments.FirstOrDefault(); }
        }

        string IOption.this[int index]
        {
            get { return arguments[index]; }
        }

        bool IOption.IsGlobal
        {
            get { return this.IsGlobal; }
        }

        #endregion

        #region IEnumerable<string> Members

        IEnumerator<string> IEnumerable<string>.GetEnumerator()
        {
            return arguments.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<string>) this).GetEnumerator();
        }

        #endregion
    }

    internal class CommandDom : ConsoleNetDomBase, ICommand
    {
        internal CommandDom(DataBucket bucket)
            : this(bucket, 
                    bucket.XCLIStructure.Element(DataBucket.MainNameSpace + "console.net").Element(
                        DataBucket.MainNameSpace + "rootcommand"),null)
        {}

        internal CommandDom(DataBucket bucket,XElement xcommand,CommandDom parentCommand)
            : base(bucket,xcommand)
        {
            ParentCommand = parentCommand;
        }

        private CommandDom ParentCommand
        {
            get; set;
        }

        internal override string Name
        {
            get
            {
                if (ParentCommand == null)
                    return "root";
                else
                    return base.Name;
            }
        }
        private CommandSetDom _Commands;
        internal CommandSetDom Commands
        {
            get
            {
                if (_Commands != null)
                    return _Commands;
                else
                    return _Commands=new CommandSetDom(Bucket,Element,this);
            }
        }

        private OptionSetDom _Options;
        internal OptionSetDom Options
        {
            get
            {
                if (_Options != null)
                    return _Options;
                else
                    return _Options = new OptionSetDom(Bucket,Element);
            }
        }

        private ArgumentSetDom _Arguments;
        internal ArgumentSetDom Arguments
        {
            get
            {
                if (_Arguments != null)
                    return _Arguments;
                else
                    return _Arguments = new ArgumentSetDom(Bucket, Element);
            }
        }


        internal string Token
        {
            get
            {
                return Element.Attribute("value").Value;
            }
        }

        internal bool Choosen
        {
            get; set;
        }





        #region ICommand Members

        string ICommand.Name
        {
            get { return this.Name; }
        }

        ICommand ICommand.ParentCommand
        {
            get { return this.ParentCommand; }
        }

        IOptionSet ICommand.Options
        {
            get { return this.Options; }
        }

        ICommandSet ICommand.Commands
        {
            get { return this.Commands; }
        }

        IArgumentSet ICommand.Arguments
        {
            get { return this.Arguments; }
        }

        ICommand ICommand.ChoosenCommand
        {
            get 
            {
                return ((IEnumerable<CommandDom>)this.Commands).SingleOrDefault((cmd) => cmd.Choosen);
            }
        }

        #endregion
    }
}